﻿// 
// $Id: _Widget.cs 3981 2010-11-02 11:12:16Z unknown $
// 
// Zen UI Toolkit Copyright (c) 2010, Paul Clancy.  All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, are permitted provided 
// that the following conditions are met:
// 
// * 	Redistributions of source code must retain the above copyright notice, this list of 
// 	conditions and the following disclaimer.
// 
// * 	Redistributions in binary form must reproduce the above copyright notice, this list of 
// 	conditions and the following disclaimer in the documentation and/or other materials 
// 	provided with the distribution.
// 
// * 	Neither the name of AppTek nor the names of its contributors may be used to endorse 
// 	or promote products derived from this software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
// THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 

#region

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

#endregion

namespace Zen.Toolkit
{
    [Designer(typeof(WidgetDesignerBase))]
    [ToolboxBitmap(typeof(Toolbar), "Resources.Icons.Toolbox.cog_error.bmp")]
    public abstract class WidgetBase : Control
    {
        private readonly HtmlTextWriterTag _baseTag;
        private readonly StateBag _controlState = new StateBag();
        private readonly List<string> _propertyConfig = new List<string>();
        private readonly List<string> _styleConfig = new List<string>();
        private ZenManager _zenManager;

        protected WidgetBase(HtmlTextWriterTag tag)
        {
            _baseTag = tag;
        }

        public int RenderOrder { get; set; }


        [Browsable(false)]
        protected StateBag ControlState
        {
            get { return ViewState; }
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        internal ZenManager ZenManager
        {
            get
            {
                if (_zenManager != null)
                {
                    return _zenManager;
                }

                if (this is ZenManager)
                {
                    return this as ZenManager;
                }

                if (!DesignMode)
                {
                    _zenManager = ZenManager.GetInstance(HttpContext.Current);
                }

                if (_zenManager == null && !DesignMode)
                {
                    throw new ZenManagerNotFoundException("Unable to find any Zenui manager on the page");
                }

                return _zenManager;
            }
        }

        //protected StateBag ControlState { get { return _controlState; } }

        protected void Push()
        {
            RenderOrder++;
        }

        protected void Pop()
        {
            RenderOrder--;
        }


        public virtual void OnBeforeGenerateScript()
        {
        }

        public virtual void OnAfterGenerateScript()
        {
        }

        public void AddConfig(string name, string value)
        {
            _propertyConfig.Add(string.Format("{0}:{1}", name, value));
        }

        public void AddConfig(string name, string format, params object[] args)
        {
            _propertyConfig.Add(string.Format("{0}:{1}", name, string.Format(format, args)));
        }

        public void AddStyle(string name, string value)
        {
            _styleConfig.Add(string.Format("{0}:{1}", name, value));
        }

        protected override void CreateChildControls()
        {
            if (!string.IsNullOrEmpty(Tooltip))
            {
                var tooltip = new Tooltip();
                tooltip.ConnectId = DojoID;
                tooltip.Label = Tooltip;
                Controls.Add(tooltip);
            }
            base.CreateChildControls();
        }

        protected virtual void RenderAttributes(HtmlTextWriter writer)
        {
            if (ID != null)
            {
                writer.AddAttribute(HtmlTextWriterAttribute.Id, ClientID);
            }
        }

        protected override void OnPreRender(EventArgs e)
        {
            GenerateScript();
            base.OnPreRender(e);
        }


        protected override void Render(HtmlTextWriter writer)
        {
            if (DesignMode)
            {
                base.Render(writer);
            }
            else
            {
                if (Visible)
                {
                    //this.EnsureID();


                    //if (string.IsNullOrEmpty(this.ClientID))
                    //    throw (new Exception("Widget has no ID"));

                    if (Z.IsZenCallback)
                    {
                        GenerateScript();
                    }

                    RenderBeginTag(writer);
                    RenderContents(writer);
                    RenderEndTag(writer);
                }
            }
        }

        public virtual void RenderBeginTag(HtmlTextWriter writer)
        {
            RenderAttributes(writer);
            writer.RenderBeginTag(_baseTag);
        }

        public virtual void RenderEndTag(HtmlTextWriter writer)
        {
            writer.RenderEndTag();
        }

        protected internal virtual void RenderContents(HtmlTextWriter writer)
        {
            base.Render(writer);
        }


        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            EnsureChildControls();
        }

        protected object GetDefaultValue(PropertyInfo property)
        {
            object[] customAttributes = property.GetCustomAttributes(typeof(DefaultValueAttribute), false);
            if (customAttributes.Length > 0)
            {
                return ((DefaultValueAttribute)customAttributes[0]).Value;
            }
            return string.Empty;
        }

        private void GenerateScript()
        {
            OnBeforeGenerateScript();

            PropertyInfo[] result =
                GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            foreach (PropertyInfo propertyInfo in result)
            {
                var styleList =
                    propertyInfo.GetCustomAttributes(typeof(DojoStyleAttribute), false) as DojoStyleAttribute[];

                if (styleList != null)
                    foreach (DojoStyleAttribute sattr in styleList)
                    {
                        object o = propertyInfo.GetValue(this, null);
                        if (o is Unit && ((Unit)o) == Unit.Empty)
                            continue;

                        _styleConfig.Add(string.Format("{0}:{1}",
                                                       sattr.Name,
                                                       o
                                             ));
                    }
            }

            if (_styleConfig.Count > 0)
            {
                _propertyConfig.Add(string.Format("style:\"{0}\"",
                                                  string.Join(";", _styleConfig)));
            }


            //var serverEvents2 = from r in result where r.PropertyType.IsAssignableFrom(typeof(ZenEventHandlers<ZenServerEvent>)) select r;
            //var clientEvents2 = from r in result where r.PropertyType.IsAssignableFrom(typeof(ZenEventHandlers<ZenClientEvent>)) select r;


            // events
            // todo: this is way too complicated
            foreach (PropertyInfo propertyInfo in result)
            {
                var serverEvents =
                    propertyInfo.GetCustomAttributes(typeof(DojoServerEventsAttribute), false) as
                    DojoServerEventsAttribute[];
                var clientEvents =
                    propertyInfo.GetCustomAttributes(typeof(DojoClientEventsAttribute), false) as
                    DojoClientEventsAttribute[];

                if (serverEvents != null)
                {
                    foreach (DojoServerEventsAttribute attr in serverEvents)
                    {
                        var inspectEvents = propertyInfo.GetValue(this, null) as ZenEventHandlers<ZenServerEvent>;

                        if (inspectEvents == null) continue;
                        PropertyInfo[] eventProps =
                            inspectEvents.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public |
                                                                  BindingFlags.NonPublic);

                        foreach (PropertyInfo eventInfo in eventProps)
                        {
                            var eventMappings =
                                eventInfo.GetCustomAttributes(typeof(DojoEventAttribute), false) as
                                DojoEventAttribute[];

                            if (eventMappings == null) continue;
                            foreach (DojoEventAttribute eventMapping in eventMappings)
                            {
                                var x = eventInfo.GetValue(inspectEvents, null) as ZenServerEvent;

                                if (x != null && x.Handler != null)
                                {
                                    _propertyConfig.Add(string.Format("{0}:{1}",
                                                                      eventMapping.Name,
                                                                      ScriptTemplate.ZenCallback.Build("",
                                                                                                       Z.
                                                                                                           ExpandClientReference
                                                                                                           (Page,
                                                                                                            x.
                                                                                                                Handler))
                                                            ));
                                }
                            }
                        }
                    }
                }

                if (clientEvents != null)
                {
                    foreach (DojoClientEventsAttribute attr in clientEvents)
                    {
                        var inspectEvents = propertyInfo.GetValue(this, null) as ZenEventHandlers<ZenClientEvent>;

                        if (inspectEvents != null)
                        {
                            PropertyInfo[] eventProps =
                                inspectEvents.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public |
                                                                      BindingFlags.NonPublic);

                            foreach (PropertyInfo eventInfo in eventProps)
                            {
                                var eventMappings =
                                    eventInfo.GetCustomAttributes(typeof(DojoEventAttribute), false) as
                                    DojoEventAttribute[];

                                if (eventMappings == null) continue;
                                foreach (DojoEventAttribute eventMapping in eventMappings)
                                {
                                    var x = eventInfo.GetValue(inspectEvents, null) as ZenClientEvent;

                                    if (x != null)
                                    {
                                        if (x.Handler != null)
                                        {
                                            _propertyConfig.Add(string.Format("{0}:{1}",
                                                                              eventMapping.Name,
                                                                              ScriptTemplate.FuncWrapper.Build(
                                                                                  Z.ExpandClientReference(Page,
                                                                                                          x.Handler))
                                                                    ));
                                        }
                                        if (x.Script != null)
                                        {
                                            _propertyConfig.Add(string.Format("{0}:{1}",
                                                                              eventMapping.Name,
                                                                                  Z.ExpandClientReference(Page,
                                                                                                          x.Script)
                                                                    ));
                                        }

                                    }
                                }
                            }
                        }
                    }
                }
            }


            foreach (PropertyInfo propertyInfo in result)
            {
                object defaultValue = null;
                var defaults =
                    propertyInfo.GetCustomAttributes(typeof(DefaultValueAttribute), false) as DefaultValueAttribute[];

                if (defaults != null && defaults.Length > 0)
                    defaultValue = defaults[0].Value;

                var list =
                    propertyInfo.GetCustomAttributes(typeof(DojoPropertyAttribute), false) as DojoPropertyAttribute[];

                if (list != null)
                {
                    foreach (DojoPropertyAttribute pattr in list)
                    {
                        object o = propertyInfo.GetValue(this, null);
                        if (o == null)
                        {
                            continue;
                        }

                        if (defaultValue != null && defaultValue.Equals(o))
                            continue;

                        if (o is Enum)
                        {
                            _propertyConfig.Add(string.Format("{0}:\"{1}\"",
                                                              pattr.Name,
                                                              o.ToString().ToLowerInvariant()
                                                    ));
                        }
                        else
                        {
                            string serializedValue = o.ToString();
                            if (!pattr.EmitNullOrEmpty && (serializedValue.Length == 0))
                                continue;

                            if ((pattr.SerializeAs & SerializationType.Url) == SerializationType.Url)
                            {
                                serializedValue = string.Format("\"{0}\"",
                                                                HttpUtility.UrlPathEncode(
                                                                    ResolveClientUrl(serializedValue))
                                    );
                            }

                            if ((pattr.SerializeAs & SerializationType.Json) == SerializationType.Json)
                            {
                                serializedValue = JsonUtils.Serialize(o);
                            }


                            _propertyConfig.Add(string.Format("{0}:{1}",
                                                              pattr.Name, serializedValue
                                                    ));
                        }
                    }
                }
            }


            Attribute[] attrs = Attribute.GetCustomAttributes(GetType());
            foreach (Attribute attr in attrs)
            {
                if (attr is DojoRequiresAttribute)
                {
                    var requiresMetaData = attr as DojoRequiresAttribute;
                    ZenManager.Script.AddRequireScript(requiresMetaData.ModuleName);
                }
                if (attr is DojoWidgetAttribute)
                {
                    var widgetMetaData = attr as DojoWidgetAttribute;


                    if (widgetMetaData.RenderPlaceHolder)
                    {
                        ZenManager.Script.AddWidgetInitWithNode(
                            this,
                            widgetMetaData.ClassMapping,
                            string.Join(",", _propertyConfig),
                            ClientID
                            );
                    }
                    else
                    {
                        ZenManager.Script.AddWidgetInit(
                            this,
                            widgetMetaData.ClassMapping,
                            string.Join(",", _propertyConfig)
                            );
                    }

                    if (widgetMetaData.InvokeStartup)
                    {
                        ZenManager.Script.AddLazyWidgetInit(
                            this, "ivs",
                            string.Format("{0}.startup()",
                                          DojoID));
                    }

                    OnAfterGenerateScript();
                }
            }
        }

        #region Common Properties

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual string DojoID
        {
            get { return "zen$" + ClientID; }
        }


        [DojoProperty(Name = "baseClass")]
        [Description("HTML class attribute")]
        [DefaultValue("")]
        //        public string @Class
        public string CssClass
        {
            get { return (string)ViewState["CssClass"] ?? ""; }
            set { ViewState["CssClass"] = value; }
        }


        [DojoProperty(Name = "label")]
        [Description("The label to display for a given widget")]
        public string Label
        {
            get { return (string)ViewState["Label"] ?? ""; }
            set { ViewState["Label"] = value; }
        }

        [DojoProperty(Name = "title")]
        [Description(
            "The label to display for a given widget. This is interchangeable with the 'label' parameter, as some widgets already have a use for the 'label', and this can be used instead to avoid conflicts"
            )]
        public string Title
        {
            get { return (string)ViewState["Title"] ?? ""; }
            set { ViewState["Title"] = value; }
        }

        public string Tooltip
        {
            get { return (string)ViewState["Tooltip"] ?? ""; }
            set { ViewState["Tooltip"] = value; }
        }


        [DojoProperty(Name = "lang")]
        [Description("Rarely used. Overrides the default Dojo locale used to render this widget")]
        public string Language
        {
            get { return (string)ViewState["Language"] ?? ""; }
            set { ViewState["Language"] = value; }
        }


        [DojoProperty(Name = "spanLabel")]
        [Description(
            "Setting spanLabel to true makes the widget take up both the label and value cells. Defaults to false")]
        [DefaultValue(false)]
        public bool SpanLabel
        {
            get
            {
                object o = ViewState["SpanLabel"];
                return (o == null) ? false : (bool)o;
            }

            set { ViewState["SpanLabel"] = value; }
        }

        #endregion
    }
}